/*
 * BoundedBuffer.h
 *
 */

#ifndef BOUNDEDBUFFER_H_
#define BOUNDEDBUFFER_H_

#include <iostream>
#include "SharedMemory2.h"
#include "Semaphore.h"

template <class T>
struct boundedBufferStruct
{
	T buffer[1000];
	int inPtr;
	int outPtr;
	int occupied;
};

template <class T>

class BoundedBuffer
{
public:
typedef enum{BLOCKING=0, NON_BLOCKING} bufferOperationType;

	BoundedBuffer(std::string file, int capacity);
	virtual ~BoundedBuffer();

	void init();

	void cleanup();

	bool put(T &element,bufferOperationType opType=BLOCKING);

	bool get(T &element,bufferOperationType opType=BLOCKING);
private:
	int m_capacity;
	SharedMemory2<boundedBufferStruct<T> > m_buffer;
	Semaphore m_mutex;
	Semaphore m_notEmpty;
	Semaphore m_notFull;
};

template <class T>
BoundedBuffer<T>::BoundedBuffer (std::string file, int capacity):
	m_capacity(capacity),m_buffer(file,'a'), m_mutex(file,'b',1),m_notEmpty(file,'c',0), m_notFull(file,'d',1)
{
};

template <class T>
BoundedBuffer<T>::~BoundedBuffer()
{
};

template <class T>
void BoundedBuffer<T>::init()
{
	boundedBufferStruct<T> shmem;

	shmem.inPtr=0;
	shmem.outPtr=0;
	shmem.occupied=0;

	m_buffer.write(shmem);

	m_mutex.init();
	m_notEmpty.init();
	m_notFull.init();

};

template <class T>
void BoundedBuffer<T>::cleanup()
{
	m_buffer.release();
	m_mutex.destroy();
	m_notEmpty.destroy();
	m_notFull.destroy();
};

template <class T>
bool BoundedBuffer<T>::put(T &element,bufferOperationType opType)
{
	int initalCapacity;

	m_mutex.dec();

	boundedBufferStruct<T> shmem = m_buffer.read();

	initalCapacity =shmem.occupied;

	std::cout <<"Elements occupied before put():"<< shmem.occupied<< " capacity:"<<m_capacity;
    std::cout <<std::endl;

	//Si el buffer esta lleno, esperar a que se libere.
	if(shmem.occupied == m_capacity)
	{
		m_mutex.inc();

		if(opType == BLOCKING )
		{
			m_notFull.dec();
			m_mutex.dec();
		}
		else
			return false;
	}

	shmem = m_buffer.read();

	shmem.buffer[shmem.inPtr] = element;
	shmem.inPtr = (shmem.inPtr+1) % m_capacity;
	shmem.occupied++;

	m_buffer.write(shmem);

	std::cout <<"Elements occupied after put():"<< shmem.occupied
			  << " inPtr:"<<shmem.inPtr<<" outPtr:" <<shmem.outPtr;
    std::cout <<std::endl;

	m_mutex.inc();

	//Si el buffer estaba vacio al insertar un elemento, indicar que ya no esta vacio.
	if(initalCapacity == 0 && opType == BLOCKING)
		m_notEmpty.inc();

	if(initalCapacity == (m_capacity - 1) && opType == BLOCKING)
		m_notFull.dec();

	return true;
};

template <class T>
bool BoundedBuffer<T>::get(T &element,bufferOperationType opType)
{
	int initalCapacity;

	m_mutex.dec();

	boundedBufferStruct<T> shmem = m_buffer.read();

	initalCapacity = shmem.occupied;

	std::cout <<"Elements occupied before get():"<< shmem.occupied<< " capacity:"<<m_capacity;
    std::cout <<std::endl;

	//Si el buffer esta lleno, esperar a que se libere.
	if(shmem.occupied == 0)
	{
		m_mutex.inc();

		if(opType == BLOCKING )
		{
			m_notEmpty.dec();
			m_mutex.dec	();
		}
		else
			return false;
	}

	shmem = m_buffer.read();

	element = shmem.buffer[shmem.outPtr];
	shmem.outPtr = (shmem.outPtr+1) % m_capacity;
	shmem.occupied--;

	m_buffer.write(shmem);

	std::cout <<"Elements occupied after get()"<< shmem.occupied
			  << " inPtr:"<<shmem.inPtr<<" outPtr:" <<shmem.outPtr;
    std::cout <<std::endl;

	m_mutex.inc();

	//Si el buffer estaba vacio al insertar un elemento, indicar que ya no esta vacio.
	if(initalCapacity == m_capacity && opType == BLOCKING)
		m_notFull.inc();

	if(initalCapacity == 1 && opType == BLOCKING)
		m_notEmpty.dec();

	return true;
};

#endif /* BOUNDEDBUFFER_H_ */
